package entites;

import java.util.ArrayList;
import java.util.List;

import utils.LANPackage;
import utils.PackageType;

import eduni.simjava.Sim_entity;
import eduni.simjava.Sim_event;
import eduni.simjava.Sim_from_p;
import eduni.simjava.Sim_port;
import eduni.simjava.Sim_predicate;
import eduni.simjava.Sim_stat;
import eduni.simjava.Sim_system;
import eduni.simjava.distributions.Sim_negexp_obj;

public class Spooler extends Sim_entity {

	private List<String> printersPortAux;
	private List<String> printersPortsFix;
	private Sim_negexp_obj delayer;
	private Sim_port toLan, toMonitor, fromSource, fromLan, fromPrioritySource;
	private Sim_stat stat;

	public Spooler(String name, double mean) {
		super(name);

		this.printersPortAux = new ArrayList<String>();
		this.printersPortsFix = new ArrayList<String>();

		// Configuração da distribuição
		this.delayer = new Sim_negexp_obj(name, mean);
		this.add_generator(this.delayer);

		// CONFIGURAÇÕES DAS PORTAS
		// Portas de saida
		this.toLan = new Sim_port("toLan");
		this.add_port(toLan);
		this.toMonitor = new Sim_port("toMonitor");
		this.add_port(toMonitor);

		// Portas de chegada
		this.fromLan = new Sim_port("fromLan");
		this.add_port(fromLan);
		this.fromSource = new Sim_port("fromSource");
		this.add_port(fromSource);
		this.fromPrioritySource = new Sim_port("fromPrioritySource");
		this.add_port(fromPrioritySource);

		// Configuração das medidas que serao feitas
		this.stat = new Sim_stat();
		this.stat.add_measure(Sim_stat.QUEUE_LENGTH);
		this.stat.add_measure(Sim_stat.RESIDENCE_TIME);
		this.stat.add_measure(Sim_stat.THROUGHPUT);
		this.stat.add_measure(Sim_stat.UTILISATION);
		this.stat.add_measure(Sim_stat.WAITING_TIME);
		this.stat.add_measure(Sim_stat.SERVICE_TIME);
		this.set_stat(this.stat);
	}

	/**
	 * Instala uma impressora. Métodos necessário pois o spooler precisa
	 * conhecer todas as impressora disponíveis.
	 * 
	 * @param printerName
	 *            Nome da impressora criada
	 */
	public void install_printer(String printerName, String printerPort) {
		this.printersPortsFix.add(printerPort);
	}

	@Override
	public void body() {

		// Reinicia a lista de impressoras entre cada simulação
		this.printersPortAux.clear();
		this.printersPortAux.addAll(printersPortsFix);

		while (Sim_system.running()) {

			Sim_event e = new Sim_event();

			// Se não tiver nenhuma impressora disponível então o spooler fica
			// esperando uma confirmação vinda da LAN e somente da LAN
			// (significa que é uma resposta de uma impressora que desocupou),
			// em seguida continua de onde parou.
			if (printersPortAux.size() <= 0) {
				this.sim_get_next(
						new Sim_from_p(Sim_system.get_entity_id("lan")), e);
			} else {
				// Senão, quem tem prioridade é escolhido.

				// Predicado que diferencia requisições com prioridade das
				// normais
				Sim_predicate predicate = new Sim_from_p(
						Sim_system.get_entity_id("priority_source"));

				// Verifica se existem requisições com prioridade na fila, se
				// tiver elas serão escolhidas, caso contrário pega uma
				// requisição sem prioridade
				if (this.sim_waiting(predicate) > 0) {
					this.sim_get_next(predicate, e);
				} else {
					this.sim_get_next(e);

				}
			}

			// Extrai o conteúdo do pacote
			if (e.get_tag() < 0) {
				this.sim_completed(e);
				break;
			}
			PackageType packetType = PackageType.values()[e.get_tag()];

			LANPackage lanPackage = (LANPackage) e.get_data();

			switch (packetType) {
			case REQUEST:
				this.schedulePrint(lanPackage);
				break;
			case RESPONSE:
				this.processeRequestAndUpdateIdlePrinters(lanPackage);
				break;
			default:
				break;
			}
			this.sim_completed(e);
		}
	}

	/**
	 * Processa e atualiza a lista de impressoras livres para novas impressões *
	 * 
	 * @param info
	 */
	private void processeRequestAndUpdateIdlePrinters(LANPackage lanPackage) {
		// Impressora esta desocupada e já pode ser usada novamente
		this.printersPortAux.add(lanPackage.get("PRINTER").toString());
		this.sim_process(this.delayer.sample());
		this.sim_schedule(toMonitor, 0.0, PackageType.RESPONSE.ordinal(),
				lanPackage);
	}

	/**
	 * Processa o documento enviado em formato de impressão, escolhe uma
	 * impressora e envia para a LAN almejando tal impressora.
	 * 
	 * @param info
	 */
	private void schedulePrint(LANPackage lanPackage) {
		String printer = this.printersPortAux.remove(0);
		this.sim_process(this.delayer.sample());

		lanPackage.put("PRINTER", printer);
		this.sim_schedule(toLan, 0.0, PackageType.PRINT_JOB.ordinal(),
				lanPackage);
	}
}
